package com.blockchain.securewallet.model;

import android.util.Log;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.wallet.DeterministicSeed;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.tx.ChainId;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class EtherHDWallet {

    private static final String TAG = "Wallet.Eth";

    public static final String PATH_ETHER = "m/44'/60'/0'/0/0";

    String privateKey;
    String publicKey;
    List<String> mnemonic;
    String mnemonicPath;
    String Address;
    String keystore;


    //path:  "m/44'/60'/0'/0/0"
    public static EtherHDWallet generateMnemonic(String path, String password) {
        if (!path.startsWith("m") && !path.startsWith("M")) {
            return null;
        }
        String[] pathArray = path.split("/");
        if (pathArray.length <= 1) {
            return null;
        }
        if (password.length() < 8) {
            return null;
        }
        String passphrase = "";
        SecureRandom secureRandom = new SecureRandom();
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed ds = new DeterministicSeed(secureRandom, 128, passphrase, creationTimeSeconds);
        return createEthWallet(ds, pathArray, password);
    }

    //path: "m/44'/60'/0'/0/0"
    public static EtherHDWallet importMnemonic(String path, List<String> list, String password) {
        if (!path.startsWith("m") && !path.startsWith("M")) {
            return null;
        }
        String[] pathArray = path.split("/");
        if (pathArray.length <= 1) {
            return null;
        }
        if (password.length() < 8) {
            //密码过短
            return null;
        }
        String passphrase = "";
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed ds = new DeterministicSeed(list, null, passphrase, creationTimeSeconds);

        return createEthWallet(ds, pathArray, password);
    }

    private static EtherHDWallet createEthWallet(DeterministicSeed ds, String[] pathArray, String password) {
        //根私钥
        byte[] seedBytes = ds.getSeedBytes();
        Log.i(TAG,"根私钥 " + Arrays.toString(seedBytes));
        //助记词
        List<String> mnemonic = ds.getMnemonicCode();
        Log.i(TAG,"助记词 " + Arrays.toString(mnemonic.toArray()));

        try {
            //助记词种子
            byte[] mnemonicSeedBytes = MnemonicCode.INSTANCE.toEntropy(mnemonic);
            Log.i(TAG,"助记词种子 " + Arrays.toString(mnemonicSeedBytes));
            ECKeyPair mnemonicKeyPair = ECKeyPair.create(mnemonicSeedBytes);
            WalletFile walletFile = Wallet.createLight(password, mnemonicKeyPair);
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            //存这个keystore 用完后删除
            String jsonStr = objectMapper.writeValueAsString(walletFile);
            Log.i(TAG,"mnemonic keystore " + jsonStr);
            //验证
            WalletFile checkWalletFile = objectMapper.readValue(jsonStr, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            byte[] checkMnemonicSeedBytes = Numeric.hexStringToByteArray(ecKeyPair.getPrivateKey().toString(16));
            Log.i(TAG,"验证助记词种子 "
                    + Arrays.toString(checkMnemonicSeedBytes));
            List<String> checkMnemonic = MnemonicCode.INSTANCE.toMnemonic(checkMnemonicSeedBytes);
            Log.i(TAG,"验证助记词 " + Arrays.toString(checkMnemonic.toArray()));

        } catch (MnemonicException.MnemonicLengthException | MnemonicException.MnemonicWordException | MnemonicException.MnemonicChecksumException | CipherException | IOException e) {
            e.printStackTrace();
        }

        if (seedBytes == null)
            return null;
        DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
        for (int i = 1; i < pathArray.length; i++) {
            ChildNumber childNumber;
            if (pathArray[i].endsWith("'")) {
                int number = Integer.parseInt(pathArray[i].substring(0,
                        pathArray[i].length() - 1));
                childNumber = new ChildNumber(number, true);
            } else {
                int number = Integer.parseInt(pathArray[i]);
                childNumber = new ChildNumber(number, false);
            }
            dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
        }
        Log.i(TAG,"path " + dkKey.getPathAsString());

        ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
        Log.i(TAG,"eth privateKey " + keyPair.getPrivateKey().toString(16));
        Log.i(TAG,"eth publicKey " + keyPair.getPublicKey().toString(16));

        EtherHDWallet ethHDWallet = null;
        try {
            WalletFile walletFile = Wallet.createLight(password, keyPair);
            Log.i(TAG,"eth address " + "0x" + walletFile.getAddress());
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            //存
            String jsonStr = objectMapper.writeValueAsString(walletFile);
            Log.i(TAG,"eth keystore " + jsonStr);

            ethHDWallet = new EtherHDWallet(keyPair.getPrivateKey().toString(16),
                    keyPair.getPublicKey().toString(16),
                    mnemonic, dkKey.getPathAsString(),
                    "0x" + walletFile.getAddress(), jsonStr);
        } catch (CipherException | JsonProcessingException e) {
            e.printStackTrace();
        }

        return ethHDWallet;
    }

    private EtherHDWallet(String privateKey, String publicKey, List<String> mnemonic, String mnemonicPath, String address, String keystore) {
        this.privateKey = privateKey;
        this.publicKey = publicKey;
        this.mnemonic = mnemonic;
        this.mnemonicPath = mnemonicPath;
        this.Address = address;
        this.keystore = keystore;
    }

    public String getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    public String getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }

    public List<String> getMnemonic() {
        return mnemonic;
    }

    public void setMnemonic(List<String> mnemonic) {
        this.mnemonic = mnemonic;
    }

    public String getMnemonicPath() {
        return mnemonicPath;
    }

    public void setMnemonicPath(String mnemonicPath) {
        this.mnemonicPath = mnemonicPath;
    }

    public String getAddress() {
        return Address;
    }

    public void setAddress(String address) {
        Address = address;
    }

    public String getKeystore() {
        return keystore;
    }

    public void setKeystore(String keystore) {
        this.keystore = keystore;
    }

    public String signedEthTransactionData(String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, Double value) {
        String strCode= "plate magnet happy diary memory speed emerge horn rhythm doctor problem crew";
        BigDecimal realValue = Convert.toWei(value.toString(), Convert.Unit.ETHER);
//        RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
//                nonce,
//                gasPrice,
//                gasLimit,
//                to,
//                realValue.toBigIntegerExact());
        Log.d("ywLog","nonce:"+nonce+"\ngasPrice:"+gasPrice+"\ngasLimit:"+gasLimit+"\nvalue:"+realValue.toBigInteger());
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce,gasPrice,gasLimit,to,realValue.toBigInteger(),"0x");
        //手续费= (gasPrice * gasLimit ) / 10^18 ether
        //Credentials credentials = WalletUtils.loadBip39Credentials("",strCode);
        Log.d("ywLog","raw:\n"+rawTransaction.getNonce()+"\n"+rawTransaction.getGasPrice()+"\n"+rawTransaction.getGasLimit()+"\n"+rawTransaction.getValue()+"\n"+rawTransaction.getData()+"\n------");
        String passphrase = "";
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
        DeterministicSeed ds = new DeterministicSeed(toList(strCode), null, passphrase, creationTimeSeconds);
        byte[] seedBytes = ds.getSeedBytes();
        Log.i(TAG,"sign root private key " + Arrays.toString(seedBytes));
        DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
        String[] pathArray = PATH_ETHER.split("/");

        for (int i = 1; i < pathArray.length; i++) {
            ChildNumber childNumber;
            if (pathArray[i].endsWith("'")) {
                int number = Integer.parseInt(pathArray[i].substring(0,
                        pathArray[i].length() - 1));
                childNumber = new ChildNumber(number, true);
            } else {
                int number = Integer.parseInt(pathArray[i]);
                childNumber = new ChildNumber(number, false);
            }
            dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
        }
        String privateKey = getPrivateKey();
        if (privateKey.startsWith("0x")) {
            privateKey = privateKey.substring(2);
        }
        ECKeyPair keyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
        //ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
        Log.d(TAG,"sign private key:"+keyPair.getPrivateKey().toString(16));
        Credentials credentials = Credentials.create(keyPair);
//        try {
//            credentials = WalletUtils.loadCredentials(password,wallet.keystore);
//        } catch (IOException e) {
//            e.printStackTrace();
//        } catch (CipherException e) {
//            e.printStackTrace();
//        }
        //使用TransactionEncoder对RawTransaction进行签名操作
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        //转换成0x开头的字符串
        return Numeric.toHexString(signedMessage);
    }

    public String signedContractTransactionData(String contractAddress,//代币的智能合约地址
                                                String toAdress,//对方的地址
                                                BigInteger nonce,//获取到交易数量
                                                BigInteger gasPrice,
                                                BigInteger gasLimit,
                                                Double value,
                                                Double decimal){
        //因为每个代币可以规定自己的小数位, 所以实际的转账值=数值 * 10^小数位
        BigDecimal realValue = BigDecimal.valueOf(value * Math.pow(10.0, decimal));
        //0xa9059cbb代表某个代币的转账方法hex(transfer) + 对方的转账地址hex + 转账的值的hex
        String data = "0xa9059cbb" + Numeric.toHexStringNoPrefixZeroPadded(Numeric.toBigInt(toAdress), 64) + Numeric.toHexStringNoPrefixZeroPadded(realValue.toBigInteger(), 64);
        RawTransaction rawTransaction = RawTransaction.createTransaction(
                nonce,
                gasPrice,
                gasLimit,
                contractAddress,
                data);

        //Credentials credentials = WalletUtils.loadBip39Credentials(password,strCode);
        Credentials credentials = null;
        try {
            credentials = WalletUtils.loadCredentials("",getKeystore());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CipherException e) {
            e.printStackTrace();
        }
        //使用TransactionEncoder对RawTransaction进行签名操作
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        //转换成0x开头的字符串
        return Numeric.toHexString(signedMessage);
    }

    private List<String> toList(String mnemonic) {
        List<String> list = new ArrayList<>();
        for(String word: mnemonic.split(" ")) {
            list.add(word);
        }
        return list;
    }
}